package com.lagou.homework.utils;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author 皮卡小丘丘 2021/2/24
 */
public class TPMonitor {
    //记录每个方法对应的耗时列表
    private static Map<String, LinkedList<Long>> metaQue =new ConcurrentHashMap<>();
    //记录每个耗时记录的时间，由于linkedList有序，因此和metaQue的耗时一一对应
    private static Map<String, LinkedList<Long>> timeQue =new ConcurrentHashMap<>();
    //<方法名，<耗时，耗时出现次数>>
    private static Map<String, TreeMap<Long,Integer>> countQue=new ConcurrentHashMap<>();
    //性能参数
    private static Map<String,Long> TP_90_MINUTE=new ConcurrentHashMap<>();
    private static  Map<String,Long> TP_99_MINUTE=new ConcurrentHashMap<>();
    //线程池，执行统计性能参数并输出
    private static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());

    /**
     * metaQue中每增加一次记录，countQue中就增加一个次数
     * @param methodName
     * @param timeSpend
     * @param time
     */
    public static void addRecord(String methodName,long timeSpend,long time){
        checkMethod(methodName);
        LinkedList<Long> list = metaQue.get(methodName);
        LinkedList<Long> times = timeQue.get(methodName);
        //记录耗时
        list.addLast(timeSpend);
        //记录调用时间
        times.addLast(time);
        TreeMap<Long, Integer> map = countQue.get(methodName);
        if(map.containsKey(timeSpend)){
            //该耗时之前出现过，则次数增加1
            map.put(timeSpend,map.get(timeSpend)+1);
        }else{
            //该耗时没有出现过，记录出现次数为1
            map.put(timeSpend,1);
        }
    }

    private static void checkMethod(String methodName) {
        //如果方法未统计过，则为其添加记录
        if(!metaQue.containsKey(methodName)){
            metaQue.put(methodName,new LinkedList<>());
            timeQue.put(methodName,new LinkedList<>());
            countQue.put(methodName,new TreeMap<>());
        }
    }

    /**
     * 整理记录，清除1分钟前的记录
     * @param methodName
     */
    private static void freshRecord(String methodName) {
        long nowTime = new Date().getTime();
        LinkedList<Long> timeRecord = timeQue.get(methodName);
        Long first = timeRecord.getFirst();
        //当之前记录中有1分钟前的记录，则清除该记录
        while (nowTime-first > 60000){
            timeRecord.removeFirst();
            //取出第一个记录的耗时
            Long timeSpend = metaQue.get(methodName).getFirst();
            metaQue.get(methodName).removeFirst();
            TreeMap<Long, Integer> map = countQue.get(methodName);
            Integer times = map.get(timeSpend);
            //如果只出现了1次，那么本次清除后该耗时记录就不存在;否则该耗时记录次数减1
            if(times.equals(1)){
                map.remove(timeSpend);
            }else
                map.put(timeSpend, times-1);
            //获取下一条记录的记录时间
            first = timeRecord.getFirst();
        }
    }

    private static void caculatePerformance(String methodName){
        //清理1分钟前的记录
        freshRecord(methodName);
        //统计当前记录中，方法调用总次数，找到10%和1%的访问次数值
        int total = metaQue.get(methodName).size();
        double tp90 = Math.ceil(total * 0.1);
        double tp99 = Math.ceil(total * 0.01);
        TreeMap<Long, Integer> counts = countQue.get(methodName);
        int record=0;
        NavigableMap<Long, Integer> map = counts.descendingMap();
        //倒叙统计，从最大耗时开始统计次数，找到1%和10%的位置的耗时值，降低复杂度
        for (Map.Entry<Long, Integer> entry : map.entrySet()) {
            record+=entry.getValue();
            if(record >= tp90){
                TP_90_MINUTE.put(methodName,entry.getKey());
                break;
            }
        }
        record=0;
        for (Map.Entry<Long, Integer> entry : map.entrySet()) {
            record+=entry.getValue();
            if(record >= tp99){
                TP_99_MINUTE.put(methodName,entry.getKey());
                break;
            }
        }
    }

    /**
     * 设置定时线程池
     * @param methods
     * @param start
     * @param delay
     */
    public static void startMonitor(Method[] methods,long start,long delay){
        executorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                for (Method method : methods) {
                    String methodName = method.getName();
                    if(metaQue.containsKey(methodName)) {
                        caculatePerformance(methodName);
                        printPerformance(methodName);
                    }
                }
            }
        },start,delay, TimeUnit.SECONDS);

    }

    /**
     * 控制台输出方法的TP90和TP99
     * @param methodName
     */
    private static void printPerformance(String methodName){
        System.out.println("===============================================");
        System.out.println(""+methodName+": 1分钟内的TP90为：" + TP_90_MINUTE.get(methodName));
        System.out.println(""+methodName+": 1分钟内的TP99为：" + TP_99_MINUTE.get(methodName));
        System.out.println("===============================================");
    }
}
